home *** CD-ROM | disk | FTP | other *** search
/ Computer Select (Limited Edition) / Computer Select.iso / dobbs / v17n05 / wincomm.exe / XMODEM.C < prev    next >
Encoding:
C/C++ Source or Header  |  1992-02-02  |  12.8 KB  |  461 lines

  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. //  XMODEM.C    - Written by Mike Sax for Dr. Dobb's Journal
  4. //
  5. //  This file contains two public functions:
  6. //      BOOL UploadXModem(HWND hParent, int gnPortID, LPSTR lpFilename);
  7. //      BOOL DownloadXModem(HWND hParent, int gnPortID, LPSTR lpFilename);
  8. //
  9. //  These functions will transfer the file specified by lpFilename on the
  10. //  Windows comm. port specified by gnPortID.  The hParent parameter is the
  11. //  handle of the window that will be the parent of the XModem status dialog.
  12. //
  13. //  To include these functions in your own program, you should include the
  14. //  following files with your program: XMODEM.C COMM.C XMODEM.RC.  No
  15. //  additional functions or variables are required.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18.  
  19. #define dprintf(s) MessageBox(GetFocus(), s, "Debug Message", MB_OK)
  20.  
  21. //  xmodem.c
  22. #define USECOMM 1               // for 3.1 windows.h
  23. #include "windows.h"
  24. #include "wincom.h"
  25. #include "comm.h"
  26. #include <stdio.h>
  27. #include <conio.h>
  28. #include <stdlib.h>
  29. #include <memory.h>
  30.  
  31. #define RETRIES  12
  32. #define CRCTRIES 2
  33. #define PADCHAR  0x1a
  34. #define SOH      1
  35. #define EOT      4
  36. #define ACK      6
  37. #define NAK      0x15
  38. #define CAN      0x18
  39. #define CRC      'C'
  40.  
  41. // Local data
  42. static int gcTries;               // retry counter
  43. static char gachBuffer[130];      // I/O buffer
  44. static int ghStatusDlg;           // Handle of the transfer status dialog
  45. static BOOL gbUserCanceled;       // Did the user press cancel?
  46. static int gnPortID;              // ID of the current comm. port
  47. static FARPROC glpDlgProc;        // ProcInstance of status dialog
  48. static BOOL gbParentEnabled;      // Parent of status dialog enabled?
  49. static int gbTimeOut;             // Time out char when reading?
  50. static DCB gDCB;                  // Comm. status that we save
  51.  
  52. // Prototypes:
  53. static void ReceiveError(int, int);
  54. static void Sleep(int);
  55. static void Status(char *);
  56. static void TestWordLen(void);
  57. HANDLE GetCurrentInstance(void);
  58. static BOOL TimeOut(void);
  59. WORD CalculateCRC(char *pchBuffer, int nLen);
  60. static BOOL CreateTransferDialog(HWND hParent, char *szTitle, char *szFilename);
  61. void DestroyTransferDialog(void);
  62. BOOL _export _far PASCAL TransferDlgProc(HWND hDlg, WORD wMessage, WORD wParam, long lParam);
  63. static void DoEvents(void);
  64. static void ModifyCommState(void);
  65. static void RestoreCommState(void);
  66.  
  67. // Error messages
  68. static char *aszError[] = 
  69.     {
  70.     "Timed Out",
  71.     "Invalid SOH",
  72.     "Invalid Block #",
  73.     "Invalid checksum/crc"
  74.     };
  75.  
  76. enum
  77.     {
  78.     errTIMEOUT,
  79.     errINVALIDSOH,
  80.     errINVALIDBLOCK,
  81.     errINVALIDCHECKSUM
  82.     };
  83.  
  84. // Give control to other windows
  85. static void DoEvents(void)
  86.     {
  87.     MSG msg;
  88.  
  89.     while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
  90.         {
  91.         if (msg.message == WM_QUIT)
  92.             {   // Our application has to quit: simulate user pressed cancel
  93.             PostQuitMessage(msg.wParam);
  94.             gbUserCanceled = TRUE;
  95.             return;
  96.             }
  97.         if (!IsDialogMessage(ghStatusDlg, &msg))
  98.             {
  99.             TranslateMessage(&msg);
  100.             DispatchMessage(&msg);
  101.             }
  102.         }
  103.     }
  104.  
  105. // Classic "pause" function for Windows
  106. static void Sleep(int nTicks)
  107.     {
  108.     DWORD dwEnd = GetTickCount() + nTicks * 55;
  109.  
  110.     while ((dwEnd > GetTickCount()) && !gbUserCanceled)
  111.         DoEvents();
  112.     }
  113.  
  114. // Wait x seconds for char in the com. port, return -1 if none, char otherwise
  115. static int ComReadCharTimeOut(int nPortID, int nSeconds)
  116.     {
  117.     DWORD dwEnd = GetTickCount() + nSeconds * 1000l;
  118.  
  119.     gbTimeOut = FALSE;
  120.     while ((dwEnd > GetTickCount()) && !gbUserCanceled)
  121.         {
  122.         if (CharsWaitingToBeRead(nPortID))
  123.             return ComReadChar(nPortID);
  124.         DoEvents();
  125.         }
  126.     gbTimeOut = TRUE;
  127.     return -1;
  128.     }
  129.  
  130. // Tansfer status dialog proc.
  131. BOOL _export _far PASCAL TransferDlgProc(HWND hDlg, WORD wMessage, WORD wParam,
  132.                                   long lParam)
  133.     {
  134.     switch(wMessage)
  135.         {
  136.         case WM_INITDIALOG:
  137.             gbUserCanceled = FALSE;
  138.             break;
  139.         case WM_COMMAND:
  140.             if (wParam != IDCANCEL)
  141.                 return FALSE;           // Let dialog perform default action
  142.         case WM_CLOSE:                  // fall thru!
  143.             gbUserCanceled = TRUE;
  144.             break;
  145.         default:
  146.             return FALSE;
  147.         }
  148.     return TRUE;
  149.     }
  150.  
  151. // Returns the instance of the current task
  152. HANDLE GetCurrentInstance(void)
  153.     {
  154.     _asm push ss
  155.     _asm call GlobalHandle
  156.     // return value in ax
  157.     }
  158.  
  159. // Create transfer dialog box.  Return TRUE if success, FALSE otherwise
  160. static BOOL CreateTransferDialog(HWND hParent, char *szTitle, char *szFilename)
  161.     {
  162.     HANDLE hInstance = GetCurrentInstance();
  163.  
  164.     glpDlgProc = MakeProcInstance((FARPROC)TransferDlgProc, hInstance);
  165.     if (NULL == glpDlgProc)
  166.         return FALSE;
  167.     ghStatusDlg = CreateDialog(hInstance, "XMDMSTATUS", hParent, glpDlgProc);
  168.     if (ghStatusDlg)
  169.         {
  170.         DoEvents();
  171.         SetWindowText(ghStatusDlg, szTitle);
  172.         SetDlgItemText(ghStatusDlg, IDD_FILENAME, szFilename);
  173.         ShowWindow(ghStatusDlg, SW_SHOW);
  174.         gbParentEnabled = !EnableWindow(hParent, FALSE);
  175.         ModifyCommState();
  176.         }
  177.     else
  178.         FreeProcInstance(glpDlgProc);
  179.     return ghStatusDlg;
  180.     }
  181.  
  182. // Destroys the transfer status dialog box
  183. void DestroyTransferDialog(void)
  184.     {
  185.     if (ghStatusDlg)
  186.         {
  187.         EnableWindow(GetParent(ghStatusDlg), gbParentEnabled);
  188.         DestroyWindow(ghStatusDlg);
  189.         FreeProcInstance(glpDlgProc);
  190.         ghStatusDlg = NULL;
  191.         RestoreCommState();
  192.         }
  193.     }
  194.  
  195. // Saves current comm. port settings and sets XOn/XOff off, N-8-1
  196. static void ModifyCommState(void)
  197.     {
  198.     DCB dcb;
  199.  
  200.     GetCommState(gnPortID, &gDCB);
  201.     dcb = gDCB;
  202.     dcb.fOutX = dcb.fInX = (BYTE)FALSE;
  203.     dcb.ByteSize = (BYTE)8;
  204.     dcb.Parity =  NOPARITY;
  205.     dcb.StopBits = (BYTE)ONESTOPBIT;
  206.     SetCommState(&dcb);
  207.     }
  208.  
  209. static void RestoreCommState(void)
  210.     {
  211.     SetCommState(&gDCB);
  212.     }
  213.  
  214. // Upload a file using the XModem protocol return TRUE if success
  215. // hParent:    the handle of the window that should be the parent of
  216. //             the transfer status dialog box (shouldn't be NULL).
  217. // gnPortID:   the port ID of the port which is used for the transfer
  218. // szFilename: the name of the file to be transfered
  219. BOOL UploadXModem(HWND hParent, int gnPortID, char *szFilename)
  220.     {
  221.     int i, nCheckSum, bEOF = FALSE, chAnswer = 0, nLength, chCRCOut = 0;
  222.     WORD wCRC;
  223.     char nBlock = 1;
  224.     BOOL bResult;
  225.     int hFile;
  226.  
  227.     hFile = _lopen(szFilename, OF_READ);
  228.     if ((gnPortID < 0) || (hFile < 1))
  229.         return FALSE;       // Port is not open or file not found
  230.     if (!CreateTransferDialog(hParent, "XMODEM Upload (Checksum)", szFilename))
  231.         {
  232.         _lclose(hFile);
  233.         return FALSE;       // Couldn't create dialog --> failure
  234.         }
  235.     gcTries = 0;
  236.     while (gcTries++ < RETRIES && chCRCOut != NAK && chCRCOut != CRC)
  237.         chCRCOut = ComReadCharTimeOut(gnPortID, 6);
  238.     gcTries = 0;
  239.     if (chCRCOut == CRC)
  240.         SetWindowText(ghStatusDlg, " XMODEM Upload (CRC)  ");
  241.     else if (chCRCOut != NAK)   // Time out or tried 10 times without succes
  242.         gcTries = RETRIES;
  243.     while (gcTries < RETRIES && !bEOF && chAnswer != CAN)
  244.         {
  245.         // read the next data block
  246.         memset(gachBuffer, 128, PADCHAR);
  247.         if ((nLength = _lread(hFile, gachBuffer, 128)) != 128)
  248.             bEOF = TRUE;
  249.         if (nLength == 0)
  250.             break;
  251.         SetDlgItemInt(ghStatusDlg, IDD_BLOCK, nBlock, FALSE);
  252.         if (gbUserCanceled)
  253.             {
  254.             ComWriteChar(gnPortID, CAN);
  255.             ComWriteChar(gnPortID, CAN);
  256.             break;
  257.             }
  258.         ComWriteChar(gnPortID, SOH);        // SOH
  259.         ComWriteChar(gnPortID, nBlock);     // block number
  260.         ComWriteChar(gnPortID, ~nBlock);    // 1s complement
  261.         nCheckSum = 0;
  262.         // send the data block
  263.         for (i = 0; i < 128; i++)
  264.             {
  265.             ComWriteChar(gnPortID, gachBuffer[i]);
  266.             nCheckSum += gachBuffer[i];     // checksum calculation
  267.             }
  268.         // Send error-correcting value (nCheckSum or crc)
  269.         if (chCRCOut == NAK)
  270.             ComWriteChar(gnPortID, nCheckSum & 255);
  271.         else
  272.             {
  273.             wCRC = CalculateCRC(gachBuffer, 130);
  274.             ComWriteChar(gnPortID, (wCRC >> 8) & 255);
  275.             ComWriteChar(gnPortID, wCRC & 255);
  276.             }
  277.         // read ACK, NAK, or CAN from receiver
  278.         chAnswer = ComReadCharTimeOut(gnPortID, 10);
  279.         if (chAnswer == ACK)
  280.             {
  281.             nBlock++;
  282.             gcTries = 0;
  283.             SetDlgItemInt(ghStatusDlg, IDD_ERRORS, 0, FALSE);
  284.             SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, "");
  285.             }
  286.         else
  287.             {
  288.             bEOF = FALSE;
  289.             SetDlgItemInt(ghStatusDlg, IDD_ERRORS, ++gcTries, FALSE);
  290.             SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, chAnswer == NAK ?
  291.                            "Invalid CRC" : "Time out");
  292.             // Position to previous block
  293.             if (_llseek(hFile, -128l, 1) == -1)
  294.                 _llseek(hFile, 0L, 0);
  295.             }
  296.         }
  297.     if (bEOF)
  298.         {
  299.         ComWriteChar(gnPortID, EOT);        // send the EOT
  300.         ComReadCharTimeOut(gnPortID, 10);   // wait for an ACK
  301.         bResult = TRUE;
  302.         }
  303.     else
  304.         bResult = FALSE;                    // transfer aborted
  305.     _lclose(hFile);
  306.     DestroyTransferDialog();
  307.     return bResult;                         // success
  308.     }
  309.  
  310.  
  311. // Download a file using the XModem protocol return TRUE if success
  312. // hParent:    the handle of the window that should be the parent of
  313. //             the transfer status dialog box.
  314. // gnPortID:   the port ID of the port which is used for the transfer
  315. // szFilename: the name of the file to be transfered
  316. BOOL DownloadXModem(HWND hParent, int gnPortID, char *szFilename)
  317.     {
  318.     int nCurrentBlock=0, nSOH= 0, nBlock, nNotBlock, i, hFile;
  319.     BOOL bCRC = TRUE, bFirst = TRUE;
  320.     WORD wOurChecksum, wChecksumSent;
  321.  
  322.     hFile = _lcreat(szFilename, 0);
  323.     if ((gnPortID < 0) || (hFile < 0))
  324.         return FALSE;       // Port is not open --> failure
  325.     if (!CreateTransferDialog(hParent, "XMODEM Download (Checksum)", szFilename))
  326.         return FALSE;       // Couldn't create dialog --> failure
  327.     if(!ghStatusDlg)
  328.         return FALSE;
  329.     // Send Cs then NAKs until the sender starts sending
  330.     gcTries = 0;
  331.     while (nSOH != SOH && gcTries < RETRIES)
  332.         {
  333.         bCRC = (gcTries++ < CRCTRIES);
  334.         ComWriteChar(gnPortID, bCRC ? CRC : NAK);
  335.         nSOH = ComReadCharTimeOut(gnPortID, 6);
  336.         if (nSOH != -1 && nSOH != SOH)
  337.             Sleep(6);
  338.         }
  339.     if (bCRC)
  340.         SetWindowText(ghStatusDlg, "XMODEM Download (CRC)");
  341.     while ((gcTries < RETRIES) && (!gbUserCanceled))
  342.         {
  343.         if (gbTimeOut)
  344.             ReceiveError(errTIMEOUT, NAK);
  345.         SetDlgItemInt(ghStatusDlg, IDD_BLOCK, nCurrentBlock + 1, FALSE);
  346.         if (!bFirst)
  347.             {
  348.             nSOH = ComReadCharTimeOut(gnPortID, 10);
  349.             if (nSOH == -1)     // TimeOut
  350.                 continue;
  351.             else if (nSOH == CAN)
  352.                 break;
  353.             else if (nSOH == EOT)
  354.                 {
  355.                 ComWriteChar(gnPortID, ACK);
  356.                 break;
  357.                 }
  358.             }
  359.         bFirst = FALSE;
  360.         nBlock  = ComReadCharTimeOut(gnPortID, 1);      // block number
  361.         nNotBlock = ComReadCharTimeOut(gnPortID, 1);    // 1's complement
  362.         // get data block
  363.         for (i = 0, wOurChecksum = 0 ; i < 128; i++)
  364.             {
  365.             int nValue;
  366.             nValue = ComReadCharTimeOut(gnPortID,1);
  367.             if (nValue < 0)     // Time out?
  368.                 break;
  369.             gachBuffer[i] = (char)nValue;
  370.             wOurChecksum = (wOurChecksum + (*(gachBuffer + i)) & 255) & 255;
  371.             }
  372.         if (i != 128)
  373.             continue;
  374.         // checksum or crc from sender
  375.         wChecksumSent = ComReadCharTimeOut(gnPortID, 1);
  376.         if (bCRC)
  377.             wChecksumSent = (wChecksumSent << 8) +
  378.                             ComReadCharTimeOut(gnPortID, 1);
  379.         if (gbTimeOut)
  380.             continue;
  381.         if (nSOH != SOH)      // Check the nSOH
  382.             {
  383.             ReceiveError(errINVALIDSOH, NAK);
  384.             continue;
  385.             }
  386.         if (LOBYTE(nBlock) == LOBYTE(nCurrentBlock))
  387.             _llseek(hFile, -128L, 1);
  388.         else if (LOBYTE(nBlock) != LOBYTE(nCurrentBlock + 1) )
  389.             {
  390.             ComWriteChar(gnPortID, CAN);
  391.             ReceiveError(errINVALIDBLOCK, CAN);
  392.             break;
  393.             }
  394.         else
  395.             nCurrentBlock++;
  396.         // Test the block # 1s complement
  397.         if (LOBYTE(nNotBlock) != LOBYTE(~nCurrentBlock))
  398.             {
  399.             ReceiveError(errINVALIDBLOCK, NAK);
  400.             continue;
  401.             }
  402.         if (bCRC)
  403.             wOurChecksum = CalculateCRC(gachBuffer, 130);
  404.         // Test wOurChecksum or crc vs one sent
  405.         if (wChecksumSent != wOurChecksum)
  406.             {
  407.             ReceiveError(errINVALIDCHECKSUM, NAK);
  408.             continue;
  409.             }
  410.         nSOH = nBlock = nNotBlock = wChecksumSent = gcTries = 0;
  411.         // Write the block to disk
  412.         _lwrite(hFile, gachBuffer, 128);
  413.         if (gbUserCanceled)
  414.             {
  415.             ComWriteChar(gnPortID, CAN);
  416.             ComWriteChar(gnPortID, CAN);
  417.             break;
  418.             }
  419.         ComWriteChar(gnPortID, ACK);
  420.         }
  421.     _lclose(hFile);
  422.     DestroyTransferDialog();
  423.     return (nSOH == EOT);   // return TRUE if transfer was succesfull
  424.     }
  425.  
  426. static void SendError(int erno)
  427.     {
  428.     ++gcTries;
  429.     SetDlgItemInt(ghStatusDlg, IDD_ERRORS, gcTries, FALSE);
  430.     SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, aszError[erno]);
  431.     }
  432.  
  433. static void ReceiveError(int erno, int rtn)
  434.     {
  435.     ++gcTries;
  436.     SetDlgItemInt(ghStatusDlg, IDD_ERRORS, gcTries, FALSE);
  437.     SetDlgItemText(ghStatusDlg, IDD_ERRORMESSAGE, aszError[erno]);
  438.     ComWriteChar(gnPortID, rtn);
  439.     Sleep(18);
  440.     FlushComm(gnPortID, 0);
  441.     FlushComm(gnPortID, 1);
  442.     }
  443.  
  444. WORD CalculateCRC(char *pchBuffer, int nLen)
  445.     {
  446.     int i;
  447.     DWORD dwCRC = 0;
  448.  
  449.     while (nLen--)
  450.         {
  451.         dwCRC |= (*pchBuffer++) & 255;
  452.         for (i = 0; i < 8; i++) 
  453.             {
  454.             dwCRC <<= 1;
  455.             if (dwCRC & 0x1000000L)
  456.                 dwCRC ^= 0x102100L;
  457.             }
  458.         }
  459.     return (WORD) (dwCRC >> 8);
  460.     }
  461.